﻿
namespace CmdArgs.Settings
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using CmdArgs.Utils;
    using CmdArgs.Validation;

    /// <summary>
    /// Represents one option's settings (immutable).
    /// Option's settings consist of aliases, default value, description and validators.
    /// </summary>
    [ImmutableObject(true)]
    public sealed class OptionSettings
    {
        private const string DefaultParameterName = "PARAM";

        /// <summary>
        /// Initializes a new instance of the <see cref="OptionSettings"/> class.
        /// </summary>
        /// <param name="description">The description - used for help generation.</param>
        /// <param name="defaultValue">The default value for this option.</param>
        /// <param name="parameterName">The option parameter name in generated help for usage. 
        /// (see <see cref="OptionSettingsBuilder.SetParameterName"/> and 
        /// <see cref="ParameterName"/>.)
        /// </param>
        /// <param name="parametersCount">The parameters count policy.</param>
        /// <param name="longNameAliases">The list of long name aliases.
        /// No long name is default. It must contain at least one long name or short name.</param>
        /// <param name="shortNameAliases">The list of short aliases.
        /// No short name is default. It must contain at least one long name or short name.</param>
        /// <param name="validators">The list of validators for this option's values.</param>
        internal OptionSettings(
            string description,
            string defaultValue,
            string parameterName,
            IOptionParametersCount parametersCount,
            IEnumerable<string> longNameAliases,
            IEnumerable<char> shortNameAliases,
            IEnumerable<IOptionValidator> validators)
        {
            Debug.Assert(
                (longNameAliases.Count() + shortNameAliases.Count()) > 0, 
                "Option must have at least one long name");

            this.LongNameAliases = longNameAliases.Copy();
            this.ShortNameAliases = shortNameAliases.Copy();
            this.Validators = validators.Copy();
            this.DefaultValue = defaultValue;
            this.Description = description;
            this.ParametersCount = parametersCount;

            if (string.IsNullOrEmpty(parameterName))
            {
                this.ParameterName = DefaultParameterName;
            }
            else
            {
                this.ParameterName = parameterName;
            }
        }

        /// <summary>
        /// Gets or sets the parameters count policy.
        /// </summary>
        public IOptionParametersCount ParametersCount { get; private set; }

        /// <summary>
        /// Gets the the parameter name.
        /// Parameter name is used only for help generation. 
        /// </summary>
        /// <remarks>
        /// When the option has parameter name, it's help output will look better e. g.:
        /// <code>-o FILE, --output=FILE</code>
        /// instead of 
        /// <code>-o PARAM, --output=PARAM</code>
        /// </remarks>
        public string ParameterName { get; private set; }

        /// <summary>
        /// Gets the description - used for help generation.
        /// </summary>
        public string Description { get; private set; }

        /// <summary>
        /// Gets the default value of this option.
        /// Used when no value is provided in input.
        /// Setting default value means that given option will always appear in result.
        /// </summary>
        public string DefaultValue { get; private set; }

        /// <summary>
        /// Gets the list of long name aliases.
        /// </summary>
        public IEnumerable<string> LongNameAliases { get; private set; }

        /// <summary>
        /// Gets the list of short name aliases.
        /// </summary>
        public IEnumerable<char> ShortNameAliases { get; private set; }

        /// <summary>
        /// Gets the list of validators.
        /// </summary>
        public IEnumerable<IOptionValidator> Validators { get; private set; }

        /// <summary>
        /// Writes the help for this option to given text writer.
        /// </summary>
        /// <param name="writer">The writer - used for output.</param>
        public void WriteHelpTo(TextWriter writer)
        {
            if (this.ShortNameAliases.Count() > 0 && this.LongNameAliases.Count() > 0)
            {
                writer.WriteLine(
                    "-{0}{1}, --{2}{3}",
                    this.ShortNameAliases.First(),
                    this.ParametersCount.GetParametersListForHelp(this.ParameterName, false),
                    this.LongNameAliases.First(),
                    this.ParametersCount.GetParametersListForHelp(this.ParameterName, true));
            }
            else if (this.ShortNameAliases.Count() > 0)
            {
                writer.WriteLine(
                    "-{0}{1}",
                    this.ShortNameAliases.First(),
                    this.ParametersCount.GetParametersListForHelp(this.ParameterName, false));
            }
            else
            {
                Debug.Assert(this.LongNameAliases.Count() > 0, "The option has no name");
                writer.WriteLine(
                    "--{0}{1}",
                    this.LongNameAliases.First(),
                    this.ParametersCount.GetParametersListForHelp(this.ParameterName, true));
            }

            if (string.IsNullOrEmpty(this.Description) == false)
            {
                writer.WriteLine("\t{0}", this.Description);
            }

            this.WriteHelpForAliases(writer);
        }

        /// <summary>
        /// Writes the help to console output.
        /// Overload of method <see cref="WriteHelpTo(TextWriter)"/>.
        /// </summary>
        public void WriteHelpToConsole()
        {
            this.WriteHelpTo(Console.Out);
        }

        // TODO - presunout tuto metodu do CommandLineParser, 
        // OptionSettings se stara o nastaveni ne parsovani.
        internal ValidationResult Validate(IEnumerable<string> values)
        {
            IList<ValidationResult> results = new List<ValidationResult>();
            foreach (var validator in this.Validators)
            {
                results.Add(validator.Validate(values, this));
            }

            return new ValidationResult(results);
        }

        /// <summary>
        /// Gets the merged list of long and short name aliases as strings.
        /// </summary>
        internal IEnumerable<string> GetAllNames()
        {
            var result = new List<string>(this.ShortNameAliases.Count() + this.LongNameAliases.Count());
            result.AddRange(this.LongNameAliases);
            result.AddRange(this.ShortNameAliases.Select(s => s.ToString()));
            return result;
        }

        private void WriteHelpForAliases(TextWriter writer)
        {
            if (this.ShortNameAliases.Count() > 1)
            {
                string aliasesList =
                    string.Join(
                        ", ",
                        this.ShortNameAliases.Skip(1).Select(a => a.ToString()).ToArray()); // <- since short names are chars we need to make strings from them
                writer.WriteLine("\tShort aliases: {0}.", aliasesList);
            }

            if (this.LongNameAliases.Count() > 1)
            {
                writer.WriteLine("\tLong aliases: {0}.", string.Join(", ", this.LongNameAliases.Skip(1).ToArray()));
            }
        }
    }
}
